/* Copyright (C) 2015-2016 HangZhou Zenzet Technology Co., Ltd.
 * All right reserved

 * File:crypto/locketdigest.c
 * Author:guojianchuan/max
 * Date:2016-06-02

 */

/* system header */
#include <string.h>

/* 3rd project header */
#include "openssl/evp.h"

/* my project header */
#include "locketdef.h"
#include "locketerrno.h"
#include "locketlog.h"
#include "locketcommon.h"
#include "locketconvert.h"

#include "lockethmac.h"

/* HMAC-KEY最大长度 */
#define LOCKET_HMACKEY_MAX_LEN   64

/* HMAC最大长度 */
#define LOCKET_HMAC_MAX_LEN   1024

/**
* @Function:LOCKET_CIPHER_CheckHmacParam
* @Author: guojianchuan/max
* @Date: 2016-06-27
* @Description: Check hmac param
* @caution: 
*/
static int LOCKET_CIPHER_CheckHmacParam(IN CIPHER_HMAC_S *pstHmac)
{
    if (NULL == pstHmac)
    {
        return ERR_COMMON_INVALID_PARAM;
    }

    if ((NULL == pstHmac->pucKey) ||
        (NULL == pstHmac->pucInput) ||
        (NULL == pstHmac->pucOutput) ||
        (0 == pstHmac->iKeyLen) ||
        ((0 == pstHmac->iInputLen) && (ENCODE_BINARY != pstHmac->iInputEncode)))
    {
        return ERR_COMMON_INVALID_PARAM;
    }

    if (ERR_COMMON_SUCCESS != LOCKET_CIPHER_CheckEncode (pstHmac->iInputEncode))
    {
        return ERR_COMMON_INVALID_PARAM;
    }

    if (ERR_COMMON_SUCCESS != LOCKET_CIPHER_CheckEncode (pstHmac->iOutputEncode))
    {
        return ERR_COMMON_INVALID_PARAM;
    }

    return ERR_COMMON_SUCCESS;
}

/**
* @Function: LOCKET_CIPHER_Hmac
* @Author: guojianchuan/max
* @Date: 2016-06-07
* @Description: Hmac sign (eg:MD5, SHA1, SHA256, SHA512)
* @caution:
*/
int LOCKET_CIPHER_Hmac(INOUT CIPHER_HMAC_S *pstHmac)
{
    int iErr = ERR_COMMON_SUCCESS;

    /* Check param */
    iErr = LOCKET_CIPHER_CheckHmacParam (pstHmac);
    if (ERR_COMMON_SUCCESS != iErr)
    {
        return iErr;
    }

    /* Get algo */
    const EVP_MD *pstMd = NULL;
    pstMd = EVP_get_digestbyname(pstHmac->szAlgoName);
    if (ERR_COMMON_SUCCESS != iErr)
    {
        iErr = ERR_COMMON_INVALID_ALGO;
        LOCKET_LOGERROR ("EVP_get_cipherbyname failed. err:%lu %s", iErr, LOCKET_ERR_GetString());
        return iErr;
    }

    /* Convert key (Default:Base64) */
    unsigned char *pRet                    = NULL;
    int iConvertKeyLen                     = 0;
    unsigned char szConvertKey[LOCKET_HMACKEY_MAX_LEN] = {0};
    pRet = LOCKET_CIPHER_OtherToByte (ENCODE_BASE64, pstHmac->pucKey,
                                      pstHmac->iKeyLen, szConvertKey, &iConvertKeyLen);
    if (NULL == pRet)
    {
        return ERR_COMMON_CONVERT_FAILED;
    }

    /* Convert input */
    unsigned char *pucConvertInput   = NULL;
    int iConvertInputLen             = 0;
    if (ENCODE_BINARY != pstHmac->iInputEncode)
    {
        pucConvertInput = (unsigned char*) LOCKET_malloc (pstHmac->iInputLen);
        if (NULL == pucConvertInput)
        {
            iErr = ERR_COMMON_NO_MEM;
            return iErr;
        }
        memset (pucConvertInput, 0, pstHmac->iInputLen);

        pRet = LOCKET_CIPHER_OtherToByte (pstHmac->iInputEncode, pstHmac->pucInput, pstHmac->iInputLen, pucConvertInput, &iConvertInputLen);
        if (NULL == pRet)
        {
            LOCKET_free ((void**) &pRet);
            return ERR_COMMON_CONVERT_FAILED;
        }
    }
    else
    {
        pucConvertInput   = pstHmac->pucInput;
        iConvertInputLen = pstHmac->iInputLen;
    }

    unsigned char szConvertOutput[LOCKET_HMAC_MAX_LEN] = {0};
    int iOutputLen = 0;
    EVP_PKEY *pstKey = NULL;
    EVP_MD_CTX *pstDigestCtx = NULL;
    do
    {
        /* Init digest */
        pstDigestCtx = EVP_MD_CTX_new ();
        if (NULL == pstDigestCtx)
        {
            iErr = ERR_COMMON_NO_MEM;
            break;
        }

        iErr = EVP_DigestInit_ex(pstDigestCtx, pstMd, NULL);
        if (ERR_COMMON_SUCCESS != iErr)
        {
            iErr = ERR_COMMON_OPENSSL_FAILED;
            LOCKET_LOGERROR ("EVP_DigestInit failed. err:%lu %s", iErr, LOCKET_ERR_GetString());
			break;
        }

        pstKey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, szConvertKey, iConvertKeyLen);
        if (NULL == pstKey)
        {
            iErr = ERR_COMMON_OPENSSL_FAILED;
            LOCKET_LOGERROR ("EVP_PKEY_new_mac_key failed. err:%lu %s", iErr, LOCKET_ERR_GetString());
	    break;
        }

        iErr = EVP_DigestSignInit(pstDigestCtx, NULL, pstMd, NULL, pstKey);
        if (ERR_COMMON_SUCCESS != iErr)
        {
            iErr = ERR_COMMON_OPENSSL_FAILED;
            LOCKET_LOGERROR ("EVP_DigestSignInit failed. err:%lu %s", iErr, LOCKET_ERR_GetString());
			break;
        }

        iErr = EVP_DigestSignUpdate(pstDigestCtx, pucConvertInput, iConvertInputLen);
        if (ERR_COMMON_SUCCESS != iErr)
        {
            iErr = ERR_COMMON_OPENSSL_FAILED;
            LOCKET_LOGERROR ("EVP_DigestSignUpdate failed. err:%lu %s", iErr, LOCKET_ERR_GetString());
			break;
        }

        iErr = EVP_DigestSignFinal(pstDigestCtx, szConvertOutput, (size_t*) &iOutputLen);
        if (ERR_COMMON_SUCCESS != iErr)
        {
            iErr = ERR_COMMON_OPENSSL_FAILED;
            LOCKET_LOGERROR ("EVP_DigestSignFinal failed. err:%lu %s", iErr, LOCKET_ERR_GetString());
			break;
        }

        if (ENCODE_BINARY != pstHmac->iOutputEncode)
        {
            pRet =  LOCKET_CIPHER_ByteToOther(pstHmac->iOutputEncode, szConvertOutput, iOutputLen, pstHmac->pucOutput, &pstHmac->iOutputLen);
            if (NULL == pRet)
            {
                iErr = ERR_COMMON_CONVERT_FAILED;
                break;
            }
        }
        else
        {
            memcpy (pstHmac->pucOutput, szConvertOutput, iOutputLen);
            pstHmac->iOutputLen = iOutputLen;
        }
    } while (0);

    EVP_MD_CTX_free (pstDigestCtx);
    if (ENCODE_BINARY != pstHmac->iInputEncode)
    {
        LOCKET_free ((void**) &pucConvertInput);
    }
    EVP_PKEY_free (pstKey);

    return iErr;
}
